
package com.demo.user.validation;

import com.demo.user.util.ApplicationContextUtil;
import com.demo.user.validation.annotation.OnValid;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

/**
 * @author xiexiaohu
 */
@Component
@Aspect
public class ValidateInterceptor {
	public static final Logger log = LoggerFactory.getLogger(ValidateInterceptor.class);
    @Resource
    private LocalValidatorFactoryBean validator;

    public ValidateInterceptor() {

        if (validator == null) {
        	try{
        		validator = ApplicationContextUtil.getBean(LocalValidatorFactoryBean.class);
        	}catch(Exception e){
        		log.debug(e.getMessage());
        	}
        }
    }


    /**
     * defination pointcut of service method stick <code>@Validated</code> in args.
     * "@annotation(annotationType)“ 匹配指定注解为切入点的方法；
     * 定义切点,切点为添加了注解的方法
     */
    @Pointcut("execution(* com.demo..*.*(..,@com.demo.user.validation.annotation.OnValid (*),..))")
    public void findValidateAnnotation() {
    }

//    @Pointcut("@annotation(com.demo.user.validation.annotation.OnValid)")
//    public void findValidateAnnotation() {
//    }

    /**
     * “@Before  在切点方法之前执行； @After  在切点方法之后执行；@AfterReturning 切点方法返回后执行； @AfterThrowing 切点方法抛异常执行；
     * “@Around 属于环绕增强，能控制切点执行前，执行后；
     * JoinPoint ：方法中的参数JoinPoint为连接点对象，它可以获取当前切入的方法的参数、代理类等信息，因此可以记录一些信息，验证一些信息等；“
     * 使用&&、||、!、三种运算符来组合切点表达式，表示与或非的关系；
     * validate the business logic before executing target method.
     */
    @Before("findValidateAnnotation()")
    public void validate(final JoinPoint jp) throws ConstraintViolationException {
        final Signature signature = jp.getSignature();
        final Object[] args = jp.getArgs();
        final MethodSignature methodSignature = (MethodSignature) signature;
        final Method targetMethod = methodSignature.getMethod();
        Set<ConstraintViolation<?>> result = new HashSet<>();
        final Annotation[][] paraAnnotations = targetMethod.getParameterAnnotations();// get the parameters annotations
        if (paraAnnotations != null && paraAnnotations.length > 0) {
            for (int i = 0; i < paraAnnotations.length; i++) {
                int paraAnnotationLength = paraAnnotations[i].length;// current parameter annotation length
                if (paraAnnotationLength == 0) { // no annotation on current parameter
                    continue;
                }
                for (int j = 0; j < paraAnnotationLength; j++) {
                    if (paraAnnotations[i][j] instanceof OnValid) {
                        OnValid validated = (OnValid) (paraAnnotations[i][j]);
                        Class<?>[] groups = (validated.value());
                        Object validatedObj = args[i];
                        executeValidate(validatedObj, groups, result);
                        break;
                    }
                }
            }
        }
        if (!result.isEmpty()) {
            throw new ConstraintViolationException(result);
        }
    }

    /**
     *
     * Description: execute the validate and set into result set.
     *
     * @param validatedObj
     * @param groups
     * @param result
     */
    private void executeValidate(final Object validatedObj, final Class<?>[] groups,
            final Set<ConstraintViolation<?>> result) {
        final Set<ConstraintViolation<Object>> constraintViolations = validator.validate(validatedObj, groups);
        if (!constraintViolations.isEmpty()) {
            result.addAll(constraintViolations);
        }
    }

}
